#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include "oled.h"
#include "oled_font.h"

// OLED命令定义
#define CMD_DISPLAYOFF 0xAE
#define CMD_DISPLAYON 0xAF
#define CMD_SETCONTRAST 0x81
#define CMD_SETDISPLAYCLOCKDIV 0xD5
#define CMD_SETMULTIPLEX 0xA8
#define CMD_SETDISPLAYOFFSET 0xD3
#define CMD_SETSTARTLINE 0x40
#define CMD_CHARGEPUMP 0x8D
#define CMD_MEMORYMODE 0x20
#define CMD_COMSCANDEC 0xC8
#define CMD_SEGREMAP 0xA0
#define CMD_SETLOWCOLUMN 0x00
#define CMD_SETHIGHCOLUMN 0x10
#define CMD_SETPAGE 0xB0
#define CMD_DISPLAYALLON_RESUME 0xA4
#define CMD_NORMALDISPLAY 0xA6

// 定义OLED设备的I2C地址
#define OLED_I2C_ADDR 0x3C

// OLED命令定义
#define OLED_CMD 0x00  // 控制字节：0x00表示命令模式
#define OLED_DATA 0x40 // 控制字节：0x40表示数据模式

// 定义显示内容的显存
static uint8_t OLED_GRAM[128][8]; // 128列，8页（行）
int i2c_fd;
// 画点
// x:0~127
// y:0~63
// t:1 填充 0,清空
void oled_DrawPoint(uint8_t x, uint8_t y, uint8_t t)
{
    uint8_t i, m, n;
    i = y / 8;
    m = y % 8;
    n = 1 << m;
    if (t)
    {
        OLED_GRAM[x][i] |= n;
    }
    else
    {
        OLED_GRAM[x][i] = ~OLED_GRAM[x][i];
        OLED_GRAM[x][i] |= n;
        OLED_GRAM[x][i] = ~OLED_GRAM[x][i];
    }
}
// 在指定位置显示一个字符,包括部分字符
// x:0~127
// y:0~63
// size1:选择字体 6x8/6x12/8x16/12x24
// mode:0,反色显示;1,正常显示
void oled_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t size1, uint8_t mode)
{
    uint8_t i, m, temp, size2, chr1;
    uint8_t x0 = x, y0 = y;
    if (size1 == 8)
        size2 = 6;
    else
        size2 = (size1 / 8 + ((size1 % 8) ? 1 : 0)) * (size1 / 2); // 得到字体一个字符对应点阵集所占的字节数
    chr1 = chr - ' ';                                              // 计算偏移后的值
    for (i = 0; i < size2; i++)
    {
        if (size1 == 8)
        {
            temp = asc2_0806[chr1][i];
        } // 调用0806字体
        else if (size1 == 12)
        {
            temp = asc2_1206[chr1][i];
        } // 调用1206字体
        else if (size1 == 16)
        {
            temp = asc2_1608[chr1][i];
        } // 调用1608字体
        else if (size1 == 24)
        {
            temp = asc2_2412[chr1][i];
        } // 调用2412字体
        else
            return;
        for (m = 0; m < 8; m++)
        {
            if (temp & 0x01)
                oled_DrawPoint(x, y, mode);
            else
                oled_DrawPoint(x, y, !mode);
            temp >>= 1;
            y++;
        }
        x++;
        if ((size1 != 8) && ((x - x0) == size1 / 2))
        {
            x = x0;
            y0 = y0 + 8;
        }
        y = y0;
    }
}

// 显示字符串
// x,y:起点坐标
// size1:字体大小
//*chr:字符串起始地址
// mode:0,反色显示;1,正常显示
void oled_ShowString(uint8_t x, uint8_t y, uint8_t *chr, uint8_t size1, uint8_t mode)
{
    while ((*chr >= ' ') && (*chr <= '~')) // 判断是不是非法字符!
    {
        oled_ShowChar(x, y, *chr, size1, mode);
        if (size1 == 8)
            x += 6;
        else
            x += size1 / 2;
        chr++;
    }
    while (x < 128)
    {
        oled_ShowChar(x, y, ' ', size1, mode);
        if (size1 == 8)
            x += 6;
        else
            x += size1 / 2;
    }
    
}
// m^n
uint32_t oled_Pow(uint8_t m, uint8_t n)
{
    uint32_t result = 1;
    while (n--)
    {
        result *= m;
    }
    return result;
}

// 显示数字
// x,y :起点坐标
// num :要显示的数字
// len :数字的位数
// size:字体大小
// mode:0,反色显示;1,正常显示
void oled_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size1, uint8_t mode)
{
    uint8_t t, temp, m = 0;
    if (size1 == 8)
        m = 2;
    for (t = 0; t < len; t++)
    {
        temp = (num / oled_Pow(10, len - t - 1)) % 10;
        if (temp == 0)
        {
            oled_ShowChar(x + (size1 / 2 + m) * t, y, '0', size1, mode);
        }
        else
        {
            oled_ShowChar(x + (size1 / 2 + m) * t, y, temp + '0', size1, mode);
        }
    }
}

// oled命令发送
void oled_command(uint8_t cmd)
{
    uint8_t buffer[2] = {OLED_CMD, cmd};
    if (write(i2c_fd, buffer, 2) != 2)
    {
        perror("Failed to send command to OLED");
    }
}

// OLED数据发送
void oled_data(uint8_t data)
{
    uint8_t buffer[2] = {OLED_DATA, data};
    if (write(i2c_fd, buffer, 2) != 2)
    {
        perror("Failed to send data to OLED");
    }
}

// 刷新OLED显示
void oled_Refresh(void)
{
    for (uint8_t page = 0; page < 8; page++)
    {
        oled_command(0xB0 + page); // 设置页地址
        oled_command(0x02);        // 设置低列地址
        oled_command(0x10);        // 设置高列地址

        for (uint8_t column = 0; column < 128; column++)
        {
            oled_data(OLED_GRAM[column][page]); // 发送对应的显存数据
        }
    }
}

// 清屏函数
void oled_Clear(void)
{
    for (uint8_t page = 0; page < 8; page++)
    {
        for (uint8_t column = 0; column < 128; column++)
        {
            OLED_GRAM[column][page] = 0; // 清除所有数据
        }
    }
    oled_Refresh(); // 更新显示
}

// 初始化OLED显示
int oled_init(void)
{
    int i = 0;
    static uint8_t command[31] = {
        0xAE, // 关闭显示
        0xD5, // 设置时钟分频因子,震荡频率
        0x80, //[3:0],分频因子;[7:4],震荡频率
        0xA8, // 设置驱动路数
        0X3F, // 默认 0X3F(1/64)
        0xD3, // 设置显示偏移
        0X00, // 默认为 0
        0x40, // 设置显示开始行 [5:0],行数.
        0x8D, // 电荷泵设置
        0x14, // bit2，开启/关闭
        0x20, // 设置内存地址模式
        0x00, //[1:0],00列地址模式; 01行地址模式; 10页地址模式; 默认10;
        0xA1, // 段重定义设置,bit0:0,0->0; 1,0->127;
        0xC8, // 设置 COM 扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
        0xDA, // 设置 COM 硬件引脚配置
        0x12, //[5:4]配置
        0x81, // 对比度设置
        0xEF, // 1~255;默认 0X7F (亮度设置,越大越亮)
        0xD9, // 设置预充电周期
        0xf1, //[3:0],PHASE 1;[7:4],PHASE 2;
        0xDB, // 设置 VCOMH 电压倍率
        0x30, //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;
        0xA4, // 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
        0xA6, // 设置显示方式;bit0:1,反相显示;0,正常显示
        0xAF, // 开启显示

        0x21, // 设置列地址起始位置与终点位置
        0x00,
        0x7F, // 0x00~0x7F = 0~127
        0x22, // 设置页地址起始位置与终点位置
        0x00,
        0x07, // 0x00~0x07 = 0~7
    };
    // 打开I2C设备文件
    i2c_fd = open("/dev/i2c-0", O_RDWR);

    if (i2c_fd < 0)
    {
        perror("Failed to open the i2c bus");
        return -1;
    }

    // 设置从设备的I2C地址
    if (ioctl(i2c_fd, I2C_SLAVE, OLED_I2C_ADDR) < 0)
    {
        perror("Failed to acquire bus access and/or talk to slave.");
        return -1;
    }
    for (i = 0; i < 31; i++)
    {
        oled_command(command[i]);
    }
    return 0;
}

void oled_ShowTime(void)
{
    static uint8_t oled_data[4][128];
    static time_t rawtime;
    static struct tm *timeinfo;
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    sprintf(oled_data[0], "%4d,%02d,%02d", timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday);
    sprintf(oled_data[1], "%02d:%02d:%02d", timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
    oled_ShowString(0, 0, oled_data[0], 16, 1);
    oled_ShowString(0, 16, oled_data[1], 16, 1);
    oled_Refresh();
}

void oled_ShowMesg(uint8_t *mesg, uint8_t len)
{
    static uint8_t oled_data[4][128] = {0};
    static uint8_t line = 3; // 指向当前显示的行
    line++;
    line %= 4;
    memcpy(oled_data[line], mesg, len);
    for (size_t i = 0; i < 4; i++)
    {
        oled_ShowString(0, i * 16, &oled_data[(line + 4 - i) % 4][1], 16, 1);
    }
    oled_Refresh();
}
